Conversation
| useState | ||
| :: forall a | ||
| . a | ||
| -> Hook (Tuple a (SetState a)) |
There was a problem hiding this comment.
I'd be interested to see if the state handling internally is fully parametric or if it still has all the sugar around copying/merging properties that component setState uses.
There was a problem hiding this comment.
I think one reason why this can't be parametric is that the setState state function is overloaded to either take the state as is or a function, which means you can't have a anything represented by a function as state.
There was a problem hiding this comment.
I believe this is the relevant section handling the state:
https://github.com/facebook/react/blob/52bea95cfce4a75bc7c1a09455abaeef50fa0700/packages/react-reconciler/src/ReactFiberHooks.js#L333-L472
The only thing I see is that the code checks if the state is a function:
https://github.com/facebook/react/blob/52bea95cfce4a75bc7c1a09455abaeef50fa0700/packages/react-reconciler/src/ReactFiberHooks.js#L455-L457
As you mention it seems the overloading is problematic. I am open to ideas or suggests for the representation on this.
There was a problem hiding this comment.
Unfortunately there's no safe way to describe the API. One option would be to enumerate JS primitives. This is conservative.
foreign import data StateValue :: Type -> Type
intValue :: Int -> StateValue Int
intValue = unsafeCoerce
stringValue :: String -> StateValue String
stringValue = unsafeCoerce
recordValue :: forall r. { | r } -> StateValue { | r }
recordValue = unsafeCoerce
useState :: forall a. StateValue a -> Hook (Tuple a (SetState a))This could potentially be overloaded with an instance chain that closes it off with a custom type error. You could then expose an unsafeUseState function which documents the caveat that you can't use anything which has a function as it's representation.
There was a problem hiding this comment.
I like the idea of enumerating the primitives, and maybe this is the best option. However, what you think about testing for a function and wrapping the passed in state in a function if detected. Maybe this is a bit too unsafe, but I think it might work.
src/React/Hook.purs
Outdated
| useEffect | ||
| :: forall a | ||
| . Effect (Effect a) | ||
| -> Maybe (Array EffectInput) |
There was a problem hiding this comment.
Is there a reason to not go ahead and lose the Maybe. There are two "empty" cases with Just []. If a user always has to pass in something they might as well pass in [].
There was a problem hiding this comment.
I believe that React behaves differently if you pass an empty array vs. not passing an array at all.
https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
Not passing an array means that the effect is fired after every render. Passing an empty array means that the effect is fired only on mount and unmount.
src/React/Hook.purs
Outdated
| callbackInput :: forall a. a -> CallbackInput | ||
| callbackInput = unsafeCoerce | ||
|
|
||
| foreign import data CallbackInput :: Type |
There was a problem hiding this comment.
Since this is the same as EffectInput, maybe there should be a shared MemoValue or something that they both use instead. The only reason this exists is to erase the type, so even Foreign would be appropriate, but I don't think it's necessary to have new types for each one.
There was a problem hiding this comment.
I started with a shared type for these. I would be up for making one type. Probably cleaner that way. Good call.
| . (Unit -> a -> b) | ||
| -> Maybe (Array MemoInput) | ||
| -> Hook (a -> b) | ||
| useMemo k = runFn2 useMemo_ k <<< Nullable.toNullable |
There was a problem hiding this comment.
I don't think useMemo necessarily computes a function. It is supposed to memoize an arbitrary value (useCallback is just a shortcut for memoizing a function).
There was a problem hiding this comment.
Gotcha. I will review this. I haven't played with it too much yet. Thanks.
| . Ref a | ||
| -> (Unit -> { | r }) | ||
| -> Maybe (Array ImperativeMethodsInput) | ||
| -> Hook Unit |
There was a problem hiding this comment.
I'm not sure about this type. useImperativeMethods depends on forwardRef, which will pass in a second argument to your component. We don't expose this anywhere, so I don't know if Ref a is the correct type. I think it would actually be very hard to make use of this in even a remotely type-safe way.
There was a problem hiding this comment.
Agreed that this may not be the best representation. I still need to work this one out a bit more. I am open to suggestions on this.
| (Hook Unit) | ||
|
|
||
| useContext :: forall a. Context a -> Hook a | ||
| useContext = runFn1 useContext_ |
There was a problem hiding this comment.
Right now, this isn't usable since Context is a foreign type here.
There was a problem hiding this comment.
In hindsight, I should have made Context a foreign type to begin with instead of wrapping it.
There was a problem hiding this comment.
Maybe it is worth making Context a foreign type? It's a breaking change, but maybe it is an idea.
There was a problem hiding this comment.
I'm OK with the breaking change. It's very minor.
There was a problem hiding this comment.
Sounds good. I've changed Context over to a foreign type.
Also, in order to render the consumer, I found I needed a createRenderPropsElement function. Was there another way to render the consumer?
On a side note, it seems refs have also been updated in React 16.3. I've incorporated those changes as well since it fits in with the hooks API.
There was a problem hiding this comment.
I've just used createLeafElement, and supplied the children prop explicitly in those cases.
There was a problem hiding this comment.
Ah, okay. Having createRenderPropsElement might be useful. What do you think? I am not sure about the name though.
| :: forall props | ||
| . ({ | props } -> Hook ReactElement) | ||
| -> { | props } | ||
| -> ReactElement |
There was a problem hiding this comment.
createHookElement like createLeafElement?
There was a problem hiding this comment.
Since this all uses createElement under the hood, this isn't parametric due to key and ref props.
There was a problem hiding this comment.
Sounds good for createHookElement. Noted on key and ref.
|
@natefaubion Thanks for taking a look at all of this! |
|
Example usage of hooks: https://github.com/ethul/purescript-react-example/tree/hooks |
| type ReservedReactPropFields r = | ||
| ( key :: String | ||
| , ref :: SyntheticEventHandler (Nullable ReactRef) | ||
| , ref :: Ref DOMRef |
There was a problem hiding this comment.
I don't think this is correct. A class component isn't going to have a DOMRef since it will be pointing to the instance. As is, the only thing that would make sense to me is Foreign. Since we are redoing refs anyway, maybe there's a better way to handle this side of the interface in a typed way... I'm not sure though.
There was a problem hiding this comment.
You're right. It could be the instance. I wouldn't mind Foreign, but what if we made an opaque type ForwardRef instead? I am not sure if there is a better way to hand this, but I am open to ideas.
|
It's definitely worth looking at the |
|
Thanks for pointing out the On another note, and as a point for discussion, I suppose I am a bit on the fence about tracking hooks via the index-monad, which seems along the lines of how |
|
Should this PR be closed? AFAIK, most people use |
Proposal for supporting React Hooks.
This code is still in the early stages and requires some ideas to be worked out further, but perhaps it is a useful start for a discussion. Any comments, questions, or suggestions are most welcome.
// @natefaubion